/** * List block tracker for UUID-aware list editing and serialization. * * Dependencies: wp.blocks, SFE.ElementPrep * Exposes: SFE.ListBlockTracker */ (function() { 'use strict'; window.MWP = window.MWP || {}; window.MWP.SFE = window.MWP.SFE || {}; const SFE = window.MWP.SFE; SFE.ManagerData = SFE.ManagerData || {}; const { createBlock, serialize: serializeBlocks } = window.wp?.blocks || {}; const UUID_ATTR_KEYS = ['mwpSfeUuid', 'mwpSfeUuidShadow']; const LIST_ITEM_TEXT_ATTR = 'data-mwp-sfe-list-item-text'; const LIST_ITEM_TEXT_CLASS = 'mwp-sfe-list-item-text'; const PLACEHOLDER_ATTR = 'data-rich-text-placeholder'; const LIST_ID_ATTR = 'data-list-id'; const LIST_ITEM_ID_ATTR = 'data-item-id'; const LIST_RUNTIME_UUID_ATTR = 'data-mwp-sfe-list-runtime-uuid'; const LIST_ITEM_RUNTIME_UUID_ATTR = 'data-mwp-sfe-list-item-runtime-uuid'; /** * Return whether one node is a nested list element. * * @param {Node|null} node Candidate DOM node. * @returns {boolean} True when the node is a nested list. */ function isNestedListNode(node) { return !!( node && node.nodeType === Node.ELEMENT_NODE && (node.tagName === 'UL' || node.tagName === 'OL') ); } /** * Return whether one node is ignorable formatting whitespace between list * structures. * * Pretty-printed block markup often leaves direct `\n` text nodes between a * list item's own text and its nested child list. Those nodes should not be * moved into the direct text surface because they become visible editing * artifacts once wrapped. * * @param {Node|null} node Candidate DOM node. * @returns {boolean} True when the node is ignorable whitespace. */ function isIgnorableListWhitespaceNode(node) { return !!( node && node.nodeType === Node.TEXT_NODE && String(node.textContent || '') .replace(/\uFEFF/g, '') .replace(/\u00A0/g, ' ') .trim() .length === 0 ); } /** * Return the direct inline text surface for one list item. * * @param {HTMLLIElement|null} liElement Candidate list item. * @returns {HTMLElement|null} Existing direct text surface, if any. */ function getDirectItemTextSurface(liElement) { if (!liElement || liElement.nodeType !== Node.ELEMENT_NODE || liElement.tagName !== 'LI') { return null; } return Array.from(liElement.children || []).find(child => ( child && child.nodeType === Node.ELEMENT_NODE && child.getAttribute(LIST_ITEM_TEXT_ATTR) === '1' )) || null; } /** * Return the DOM attribute name that stores one session-scoped runtime UUID. * * Runtime UUIDs are distinct from the existing serialization/attr UUIDs. They * exist only while the editor session is open so external callers can point at * the current live list cursor/target without having to manage shifting paths. * * @param {string} type Supported runtime identity type. * @returns {string} Attribute name, or an empty string for invalid types. */ function getRuntimeUuidAttributeName(type) { if (type === 'list') { return LIST_RUNTIME_UUID_ATTR; } if (type === 'item') { return LIST_ITEM_RUNTIME_UUID_ATTR; } return ''; } /** * Normalize one runtime UUID candidate. * * @param {*} value Candidate runtime UUID. * @returns {string} Trimmed runtime UUID, or an empty string. */ function normalizeRuntimeUuid(value) { return String(value || '').trim(); } /** * Return whether one element matches the requested runtime-identity type. * * @param {Element|null} element Candidate DOM element. * @param {string} type Supported runtime identity type. * @returns {boolean} True when the element matches the type. */ function isRuntimeIdentityElement(element, type) { if (!element || element.nodeType !== Node.ELEMENT_NODE) { return false; } if (type === 'list') { return element.tagName === 'UL' || element.tagName === 'OL'; } if (type === 'item') { return element.tagName === 'LI'; } return false; } /** * Return one tracked element by runtime UUID from the live DOM. * * This intentionally queries the current DOM first instead of trusting only a * cached map so history restores and other structural replacements can keep * runtime UUID resolution stable as long as the attributes remain present. * * @param {Object|null} tracker Active list tracker. * @param {string} runtimeUuid Session-scoped runtime UUID. * @param {string} type Supported runtime identity type. * @returns {Element|null} Matching live DOM element. */ function findElementByRuntimeUuid(tracker, runtimeUuid, type) { const normalizedRuntimeUuid = normalizeRuntimeUuid(runtimeUuid); const attrName = getRuntimeUuidAttributeName(type); if ( !tracker?.listElement || !normalizedRuntimeUuid || !attrName || !isRuntimeIdentityElement(tracker.listElement, 'list') ) { return null; } if ( type === 'list' && tracker.listElement.getAttribute(attrName) === normalizedRuntimeUuid ) { return tracker.listElement; } try { const selector = `[${attrName}="${CSS.escape(normalizedRuntimeUuid)}"]`; return tracker.listElement.querySelector(selector); } catch (error) { const escapedUuid = normalizedRuntimeUuid.replace(/"/g, '\\"'); return tracker.listElement.querySelector(`[${attrName}="${escapedUuid}"]`); } } /** * Return every direct text surface currently attached to one list item. * * Merge/delete flows can temporarily move multiple direct text surfaces into * the same list item. The shared list normalizer must collapse them back to * one canonical surface before placeholder syncing or serialization. * * @param {HTMLLIElement|null} liElement Candidate list item. * @returns {HTMLElement[]} Direct text surfaces in DOM order. */ function getAllDirectItemTextSurfaces(liElement) { if (!liElement || liElement.nodeType !== Node.ELEMENT_NODE || liElement.tagName !== 'LI') { return []; } return Array.from(liElement.children || []).filter(child => ( child && child.nodeType === Node.ELEMENT_NODE && child.getAttribute(LIST_ITEM_TEXT_ATTR) === '1' )); } /** * Return whether one node is only placeholder/caret scaffolding. * * Duplicate direct text surfaces may carry empty placeholder anchors or * caret `
` nodes. Those artifacts should be dropped when collapsing * duplicate surfaces instead of being concatenated into visible stacks. * * @param {Node|null} node Candidate DOM node. * @returns {boolean} True when the node is redundant placeholder UI. */ function isRedundantSurfaceArtifact(node) { if (!node) { return true; } if (isIgnorableListWhitespaceNode(node)) { return true; } if (node.nodeType === Node.TEXT_NODE) { return String(node.textContent || '') .replace(/\uFEFF/g, '') .replace(/\u00A0/g, ' ') .trim() .length === 0; } if (node.nodeType !== Node.ELEMENT_NODE) { return false; } if (node.tagName === 'BR') { return true; } return !!node.hasAttribute?.(PLACEHOLDER_ATTR); } /** * Ensure one list item owns one direct inline text surface. * * The root list remains the single live editor host, but this wrapper gives * schema/ABE flows one stable DOM surface per list item's own text content. * * @param {HTMLLIElement|null} liElement Candidate list item. * @returns {HTMLElement|null} Ensured direct text surface. */ function ensureDirectItemTextSurface(liElement) { if (!liElement || liElement.nodeType !== Node.ELEMENT_NODE || liElement.tagName !== 'LI') { return null; } let surface = getDirectItemTextSurface(liElement); if (!surface) { surface = document.createElement('span'); surface.setAttribute(LIST_ITEM_TEXT_ATTR, '1'); surface.classList.add(LIST_ITEM_TEXT_CLASS); liElement.insertBefore( surface, Array.from(liElement.childNodes || []).find(isNestedListNode) || null ); } getAllDirectItemTextSurfaces(liElement) .filter(candidate => candidate && candidate !== surface) .forEach(duplicateSurface => { Array.from(duplicateSurface.childNodes || []).forEach(node => { if (isRedundantSurfaceArtifact(node)) { node.remove(); return; } surface.appendChild(node); }); duplicateSurface.remove(); }); const childNodes = Array.from(liElement.childNodes || []); childNodes.forEach(node => { if ( node && node !== surface && !isNestedListNode(node) && isIgnorableListWhitespaceNode(node) ) { node.remove(); } }); const movableNodes = childNodes.filter(node => { if (!node || node === surface || isNestedListNode(node)) { return false; } if (isIgnorableListWhitespaceNode(node)) { return false; } return !( node.nodeType === Node.ELEMENT_NODE && node.getAttribute?.(LIST_ITEM_TEXT_ATTR) === '1' ); }); movableNodes.forEach(node => surface.appendChild(node)); return surface; } /** * Clone list attrs while optionally stripping plugin UUID ownership. * * The outermost core/list is the only persisted UUID owner for an entire * list tree. Nested core/list blocks are structural children of that root. * If we preserve a nested list UUID here, one accidental assignment can be * re-serialized forever and split a single logical list into multiple * history/edit targets. Keep this guard unless the ownership model changes * everywhere else in PHP and JS at the same time. * * @param {Object} attrs Parsed Gutenberg attrs. * @param {boolean} allowUuidOwnership True for the root list only. * @returns {Object} Safe cloned attrs. */ function cloneListAttrs(attrs, allowUuidOwnership = true) { const clonedAttrs = JSON.parse(JSON.stringify(attrs || {})); if (allowUuidOwnership) { return clonedAttrs; } UUID_ATTR_KEYS.forEach(key => delete clonedAttrs[key]); return clonedAttrs; } /** * Return the direct list-item children for one list element. * * @param {HTMLElement|null} listElement Candidate list element. * @returns {HTMLLIElement[]} Direct child list items. */ function getDirectListItems(listElement) { if (!listElement || listElement.nodeType !== Node.ELEMENT_NODE) { return []; } return Array.from(listElement.children || []).filter(child => child.tagName === 'LI'); } /** * Normalize one path-like value into zero-based list indexes. * * Supported inputs: * - `0_1_2` * - `1.2.3` * - arrays of integers * * @param {string|Array|null} pathValue Candidate path value. * @returns {number[]|null} Parsed zero-based indexes. */ function normalizePathIndexes(pathValue) { if (Array.isArray(pathValue)) { const indexes = pathValue.map(value => Number.parseInt(value, 10)); return indexes.every(Number.isInteger) && indexes.every(index => index >= 0) ? indexes : null; } const raw = typeof pathValue === 'string' ? pathValue.trim() : ''; if (!raw) { return []; } const separator = raw.includes('.') ? '.' : '_'; const parts = raw.split(separator).filter(Boolean); if (!parts.length) { return []; } const indexes = parts.map(part => Number.parseInt(part, 10)); if (!indexes.every(Number.isInteger)) { return null; } if (separator === '.') { return indexes.every(index => index > 0) ? indexes.map(index => index - 1) : null; } return indexes.every(index => index >= 0) ? indexes : null; } /** * Convert one zero-based path index list into public path metadata. * * @param {number[]} indexes Zero-based indexes. * @returns {{path: string, pathLabel: string, depth: number}} Path metadata. */ function buildPathMeta(indexes) { const safeIndexes = Array.isArray(indexes) ? indexes.filter(Number.isInteger) : []; return { path: safeIndexes.join('_'), pathLabel: safeIndexes.map(index => index + 1).join('.'), depth: Math.max(0, safeIndexes.length - 1), }; } /** * Return the direct child list element for one list item. * * @param {HTMLLIElement|null} listItem Candidate list item. * @returns {HTMLElement|null} Direct nested list, if present. */ function getDirectChildList(listItem) { if (!listItem || listItem.nodeType !== Node.ELEMENT_NODE || listItem.tagName !== 'LI') { return null; } return Array.from(listItem.children || []).find(child => ( child.tagName === 'UL' || child.tagName === 'OL' )) || null; } /** * Return the preferred nested list tag for one list item. * * @param {Object|null} tracker Active list tracker. * @param {HTMLLIElement} listItem Parent list item. * @returns {string} `UL` or `OL`. */ function getPreferredChildListTagName(tracker, listItem) { const directChildList = getDirectChildList(listItem); if (directChildList) { return directChildList.tagName; } const parentList = listItem?.parentElement; if (parentList && (parentList.tagName === 'UL' || parentList.tagName === 'OL')) { return parentList.tagName; } return tracker?.listElement?.tagName === 'OL' ? 'OL' : 'UL'; } /** * Return one direct child list for a list item, creating it only when needed. * * Outdent can promote one item and then re-home its trailing siblings beneath * that promoted item. When the promoted item already owns a child list, those * siblings must be appended into the existing list so the DOM mirrors native * editor behavior instead of creating duplicate sibling list wrappers. * * @param {Object|null} tracker Active list tracker. * @param {HTMLLIElement} listItem Parent list item. * @param {string} preferredTagName Fallback list tag name. * @returns {HTMLElement|null} Direct child list element. */ function ensureDirectChildList(tracker, listItem, preferredTagName = '') { if (!listItem || listItem.nodeType !== Node.ELEMENT_NODE || listItem.tagName !== 'LI') { return null; } let childList = getDirectChildList(listItem); if (childList) { return childList; } childList = document.createElement( preferredTagName || getPreferredChildListTagName(tracker, listItem) ); childList.classList.add('wp-block-list'); listItem.appendChild(childList); return childList; } /** * Remove empty nested list wrappers up the ancestry chain. * * The root list element is never removed, even when it becomes empty. * * @param {Object|null} tracker Active list tracker. * @param {HTMLElement|null} startList First candidate nested list. * @returns {void} */ function cleanupEmptyAncestorLists(tracker, startList) { let currentList = startList; while ( currentList && currentList !== tracker?.listElement && currentList.nodeType === Node.ELEMENT_NODE && (currentList.tagName === 'UL' || currentList.tagName === 'OL') && !getDirectListItems(currentList).length ) { const parentItem = currentList.parentElement?.tagName === 'LI' ? currentList.parentElement : null; currentList.remove(); currentList = parentItem ? parentItem.parentElement : null; } } /** * Build one new list item element from a structural operation payload. * * @param {Object|null} operation Candidate operation payload. * @returns {HTMLLIElement} New list item element. */ function buildListItemFromOperation(operation) { const li = document.createElement('li'); const directSurface = ensureDirectItemTextSurface(li); const runtimeUuid = normalizeRuntimeUuid( operation?.itemUuid ?? operation?.newItemUuid ); const html = typeof operation?.contentHtml === 'string' ? operation.contentHtml : (typeof operation?.html === 'string' ? operation.html : ''); const text = typeof operation?.contentText === 'string' ? operation.contentText : (typeof operation?.text === 'string' ? operation.text : ''); if (html) { directSurface.innerHTML = html; } else if (text) { directSurface.textContent = text; } if (runtimeUuid) { li.setAttribute(LIST_ITEM_RUNTIME_UUID_ATTR, runtimeUuid); } return li; } /** * Copy one donor item's structural/style attributes onto the destination list * item while preserving the destination runtime UUID. * * Native Enter list splitting happens inside the browser's contenteditable * engine, so the new sibling inherits the source `li` element's attributes * such as class and style automatically. API-driven insert/move operations * should mirror that behavior by cloning the donor item's `li` attributes, * except for the session-scoped runtime UUID which must stay unique. * * @param {HTMLLIElement|null} listItem Destination list item. * @param {HTMLLIElement|null} donorItem Style/structure donor item. * @returns {void} */ function copyDonorItemAttributes(listItem, donorItem) { if ( !listItem || listItem.nodeType !== Node.ELEMENT_NODE || listItem.tagName !== 'LI' || !donorItem || donorItem.nodeType !== Node.ELEMENT_NODE || donorItem.tagName !== 'LI' ) { return; } const destinationRuntimeUuid = normalizeRuntimeUuid( listItem.getAttribute(LIST_ITEM_RUNTIME_UUID_ATTR) ); Array.from(listItem.attributes || []).forEach(attr => { if (attr?.name === LIST_ITEM_RUNTIME_UUID_ATTR) { return; } listItem.removeAttribute(attr.name); }); Array.from(donorItem.attributes || []).forEach(attr => { if (attr?.name === LIST_ITEM_RUNTIME_UUID_ATTR) { return; } listItem.setAttribute(attr.name, attr.value); }); if (destinationRuntimeUuid) { listItem.setAttribute(LIST_ITEM_RUNTIME_UUID_ATTR, destinationRuntimeUuid); } } /** * Reassign one list item's structural ID from its destination styling * context. * * When an explicit target item is known, its structural `data-item-id` * becomes the style donor for insert-before, insert-after, move-before, and * move-after commands. This keeps the inheritance rule simple and matches the * public API's explicit `targetItemUuid` model. * * For internal list-path insert/move cases that do not resolve through one * target item, fall back to the local destination neighbors. If there is no * neighboring item at all, clear the structural ID so the next tracker rebuild * seeds a fresh item identity instead of accidentally preserving source attrs. * * @param {HTMLLIElement|null} listItem Destination list item. * @param {HTMLLIElement|null} targetItem Explicit style donor item. * @returns {string} Applied structural ID or an empty string. */ function inheritDestinationItemId(listItem, targetItem = null) { if (!listItem || listItem.nodeType !== Node.ELEMENT_NODE || listItem.tagName !== 'LI') { return ''; } const explicitTargetItem = targetItem && targetItem.nodeType === Node.ELEMENT_NODE && targetItem.tagName === 'LI' ? targetItem : null; const previousItem = listItem.previousElementSibling?.tagName === 'LI' ? listItem.previousElementSibling : null; const nextItem = listItem.nextElementSibling?.tagName === 'LI' ? listItem.nextElementSibling : null; const inheritedId = normalizeRuntimeUuid( explicitTargetItem?.getAttribute(LIST_ITEM_ID_ATTR) || previousItem?.getAttribute(LIST_ITEM_ID_ATTR) || nextItem?.getAttribute(LIST_ITEM_ID_ATTR) ); if (inheritedId) { listItem.setAttribute(LIST_ITEM_ID_ATTR, inheritedId); return inheritedId; } listItem.removeAttribute(LIST_ITEM_ID_ATTR); return ''; } /** * Apply one remove-list-item operation. * * @param {Object|null} tracker Active list tracker. * @param {HTMLLIElement} listItem Target list item. * @returns {boolean} True when the mutation applied. */ function applyRemoveListItemOperation(tracker, listItem) { if (!tracker?.listElement || !listItem) { return false; } const oldParentList = listItem.parentElement; listItem.remove(); cleanupEmptyAncestorLists(tracker, oldParentList); return true; } /** * Apply one indent-list-item operation. * * @param {Object|null} tracker Active list tracker. * @param {HTMLLIElement} listItem Target list item. * @returns {boolean} True when the mutation applied. */ function applyIndentListItemOperation(tracker, listItem) { if (!tracker?.listElement || !listItem) { return false; } const previousItem = listItem.previousElementSibling?.tagName === 'LI' ? listItem.previousElementSibling : null; if (!previousItem) { return false; } const nestedList = ensureDirectChildList(tracker, previousItem); nestedList.appendChild(listItem); return true; } /** * Apply one outdent-list-item operation. * * @param {Object|null} tracker Active list tracker. * @param {HTMLLIElement} listItem Target list item. * @returns {boolean} True when the mutation applied. */ function applyOutdentListItemOperation(tracker, listItem) { if (!tracker?.listElement || !listItem) { return false; } const parentList = listItem.parentElement; const parentItem = parentList?.parentElement?.tagName === 'LI' ? parentList.parentElement : null; if (!parentList || !parentItem) { return false; } const ancestorList = parentItem.parentElement; const followingSiblings = []; let next = listItem.nextElementSibling; while (next) { followingSiblings.push(next); next = next.nextElementSibling; } ancestorList.insertBefore(listItem, parentItem.nextElementSibling); if (followingSiblings.length) { const nestedList = ensureDirectChildList(tracker, listItem, parentList.tagName); followingSiblings.forEach(sibling => nestedList.appendChild(sibling)); } cleanupEmptyAncestorLists(tracker, parentList); return true; } /** * Apply one toggle-list-type operation. * * @param {Object|null} tracker Active list tracker. * @param {Object} rawOperation Structural operation payload. * @param {Object} options Apply-operation options. * @returns {boolean} True when the mutation applied. */ function applyToggleListTypeOperation(tracker, rawOperation, options = {}) { if (!tracker?.listElement) { return false; } const listPath = rawOperation.listPath ?? rawOperation.list_path ?? ''; const targetList = getListByPath(tracker, listPath); const editorHost = options?.editorHost && typeof options.editorHost.changeListType === 'function' ? options.editorHost : null; const requestedType = targetList ? ( normalizeListTypeTagName( rawOperation.value ?? rawOperation.listType ?? rawOperation.list_type ?? rawOperation.ordered ) || (targetList.tagName === 'OL' ? 'UL' : 'OL') ) : ''; if ( !targetList || !requestedType || targetList.tagName === requestedType || !editorHost ) { return false; } const nextList = editorHost.changeListType(targetList, requestedType.toLowerCase(), { saveHistory: options.saveHistory !== false, restoreCursor: options.restoreCursor !== false, }); return !!nextList; } /** * Apply one update-list-item-text operation. * * @param {HTMLLIElement} listItem Target list item. * @param {Object} rawOperation Structural operation payload. * @returns {boolean} True when the mutation applied. */ function applyUpdateListItemTextOperation(listItem, rawOperation) { if (!listItem) { return false; } const directSurface = ensureDirectItemTextSurface(listItem); const html = typeof rawOperation?.contentHtml === 'string' ? rawOperation.contentHtml : (typeof rawOperation?.html === 'string' ? rawOperation.html : ''); const text = typeof rawOperation?.contentText === 'string' ? rawOperation.contentText : (typeof rawOperation?.text === 'string' ? rawOperation.text : ''); if (!directSurface) { return false; } directSurface.innerHTML = ''; if (html) { directSurface.innerHTML = html; } else if (text) { directSurface.textContent = text; } return true; } /** * Insert one new list item relative to explicit before/after/list anchors. * * @param {Object|null} tracker Active list tracker. * @param {Object} rawOperation Structural operation payload. * @param {Object} trackerApi List tracker API surface. * @returns {boolean} True when the mutation applied. */ function applyInsertListItemOperation(tracker, rawOperation, trackerApi) { if (!tracker?.listElement || !trackerApi) { return false; } const beforePath = rawOperation.beforePath ?? rawOperation.before_path ?? ''; const afterPath = rawOperation.afterPath ?? rawOperation.after_path ?? ''; const listPath = rawOperation.listPath ?? rawOperation.list_path ?? ''; const beforeItem = trackerApi.getItemByPath(tracker, beforePath); const afterItem = trackerApi.getItemByPath(tracker, afterPath); const newListItem = buildListItemFromOperation(rawOperation); if (beforeItem?.parentElement) { beforeItem.parentElement.insertBefore(newListItem, beforeItem); copyDonorItemAttributes(newListItem, beforeItem); inheritDestinationItemId(newListItem, beforeItem); return true; } if (afterItem?.parentElement) { afterItem.parentElement.insertBefore(newListItem, afterItem.nextElementSibling); copyDonorItemAttributes(newListItem, afterItem); inheritDestinationItemId(newListItem, afterItem); return true; } const targetList = getListByPath(tracker, listPath); if (!targetList) { return false; } const position = typeof rawOperation.position === 'string' ? rawOperation.position.trim().toLowerCase() : 'append'; if (position === 'prepend' && targetList.firstElementChild) { targetList.insertBefore(newListItem, targetList.firstElementChild); } else { targetList.appendChild(newListItem); } inheritDestinationItemId(newListItem); return true; } /** * Move one existing list item relative to explicit before/after/list anchors. * * @param {Object|null} tracker Active list tracker. * @param {HTMLLIElement} listItem Target list item. * @param {Object} rawOperation Structural operation payload. * @param {Object} trackerApi List tracker API surface. * @returns {boolean} True when the mutation applied. */ function applyMoveListItemOperation(tracker, listItem, rawOperation, trackerApi) { if (!tracker?.listElement || !listItem || !trackerApi) { return false; } const beforePath = rawOperation.beforePath ?? rawOperation.before_path ?? ''; const afterPath = rawOperation.afterPath ?? rawOperation.after_path ?? ''; const listPath = rawOperation.listPath ?? rawOperation.list_path ?? ''; const beforeItem = trackerApi.getItemByPath(tracker, beforePath); const afterItem = trackerApi.getItemByPath(tracker, afterPath); const oldParentList = listItem.parentElement; let didApply = false; if (beforeItem && beforeItem !== listItem && !listItem.contains(beforeItem)) { beforeItem.parentElement.insertBefore(listItem, beforeItem); copyDonorItemAttributes(listItem, beforeItem); inheritDestinationItemId(listItem, beforeItem); didApply = true; } else if (afterItem && afterItem !== listItem && !listItem.contains(afterItem)) { afterItem.parentElement.insertBefore(listItem, afterItem.nextElementSibling); copyDonorItemAttributes(listItem, afterItem); inheritDestinationItemId(listItem, afterItem); didApply = true; } else if (typeof listPath === 'string') { const targetList = getListByPath(tracker, listPath); const ownerItem = targetList?.parentElement?.tagName === 'LI' ? targetList.parentElement : null; if (targetList && ownerItem !== listItem && !listItem.contains(ownerItem || null)) { const position = typeof rawOperation.position === 'string' ? rawOperation.position.trim().toLowerCase() : 'append'; if (position === 'prepend' && targetList.firstElementChild) { targetList.insertBefore(listItem, targetList.firstElementChild); } else { targetList.appendChild(listItem); } inheritDestinationItemId(listItem); didApply = true; } } if (didApply) { cleanupEmptyAncestorLists(tracker, oldParentList); } return didApply; } /** * Resolve one tracked list element from a list-path payload. * * The root list lives at the empty path. Nested lists are addressed by the * tree path of the parent item that owns that child list. * * @param {Object|null} tracker Active list tracker. * @param {string|Array} pathValue Root-empty list path or parent-item path. * @returns {HTMLElement|null} Matching list element. */ function getListByPath(tracker, pathValue) { if (!tracker?.listElement) { return null; } if ( pathValue === '' || pathValue === null || typeof pathValue === 'undefined' || (Array.isArray(pathValue) && !pathValue.length) || (typeof pathValue === 'string' && !pathValue.trim()) ) { return tracker.listElement; } const parentItem = ListBlockTracker.getItemByPath(tracker, pathValue); return parentItem ? getDirectChildList(parentItem) : null; } /** * Normalize one requested list-type value to a DOM tag name. * * @param {*} value Candidate list-type value. * @returns {string} `OL`, `UL`, or an empty string. */ function normalizeListTypeTagName(value) { if (value === true) { return 'OL'; } if (value === false) { return 'UL'; } const normalized = String(value || '').trim().toLowerCase(); if (normalized === 'ordered' || normalized === 'ol' || normalized === 'true') { return 'OL'; } if (normalized === 'unordered' || normalized === 'ul' || normalized === 'false') { return 'UL'; } return ''; } const ListBlockTracker = { active: null, /** * Initialize tracker for a list element * @param {HTMLElement} listElement - The UL or OL element * @param {Object} originalBlock - The complete WordPress block structure */ init(listElement, originalBlock = {}) { if (!createBlock || !serializeBlocks) { console.error('wp.blocks not available'); } const tracker = { listElement, originalBlock: JSON.parse(JSON.stringify(originalBlock)), uuidMap: new Map(), // uuid -> {type, attrs, element} domMap: new WeakMap(), // element -> uuid runtimeUuidMap: new Map(), // runtimeUuid -> {type, element} runtimeDomMap: new WeakMap() // element -> runtimeUuid }; // Attach tracker to element to avoid singleton issues listElement._mwpListTracker = tracker; // Build UUID tracking from DOM and original block structure this.buildFromDOM(tracker, listElement, originalBlock); this.active = tracker; return tracker; }, /** * Build UUID mappings from DOM and original block structure * Assigns UUIDs to all lists and list items, mapping to their original attrs */ buildFromDOM(tracker, listElement, originalBlock) { const previousEntries = tracker.uuidMap instanceof Map ? new Map(tracker.uuidMap) : new Map(); const previousRuntimeEntries = tracker.runtimeUuidMap instanceof Map ? new Map(tracker.runtimeUuidMap) : new Map(); tracker.uuidMap.clear(); tracker.domMap = new WeakMap(); tracker.runtimeUuidMap.clear(); tracker.runtimeDomMap = new WeakMap(); tracker.listElement = listElement; this.syncEditableTextSurfaces(listElement); this.registerRuntimeIdentity( tracker, listElement, 'list', previousRuntimeEntries ); // Assign UUID to root list and map to original attrs const rootUuid = this.getOrCreateUuid(listElement, 'list'); const previousRootEntry = previousEntries.get(rootUuid); tracker.uuidMap.set(rootUuid, { type: 'list', attrs: previousRootEntry?.attrs ? cloneListAttrs(previousRootEntry.attrs, true) : cloneListAttrs(originalBlock.attrs || {}, true), element: listElement }); tracker.domMap.set(listElement, rootUuid); // Recursively process list structure this.processListRecursive( tracker, listElement, originalBlock.innerBlocks || [], previousEntries, previousRuntimeEntries ); }, /** * Recursively process list items and nested lists, assigning UUIDs */ processListRecursive( tracker, listElement, originalItems, previousEntries = new Map(), previousRuntimeEntries = new Map() ) { const items = getDirectListItems(listElement); items.forEach((li, index) => { this.syncEditableTextSurfaces(li); const originalItem = originalItems[index] || {}; this.registerRuntimeIdentity( tracker, li, 'item', previousRuntimeEntries ); const itemUuid = this.getOrCreateUuid(li, 'item'); const previousItemEntry = previousEntries.get(itemUuid); // Map UUID to original item attrs tracker.uuidMap.set(itemUuid, { type: 'item', attrs: previousItemEntry?.attrs ? JSON.parse(JSON.stringify(previousItemEntry.attrs || {})) : JSON.parse(JSON.stringify(originalItem.attrs || {})), element: li }); tracker.domMap.set(li, itemUuid); // Handle nested lists const nestedList = getDirectChildList(li); if (nestedList) { const originalNested = originalItem.innerBlocks?.[0] || {}; this.registerRuntimeIdentity( tracker, nestedList, 'list', previousRuntimeEntries ); const nestedUuid = this.getOrCreateUuid(nestedList, 'list'); const previousNestedEntry = previousEntries.get(nestedUuid); // Map nested list attrs without plugin UUID ownership. // The tracker still needs a temporary DOM identity for list // editing, but persisting mwpSfeUuid* on nested lists would // fracture one logical list into multiple save/history roots. tracker.uuidMap.set(nestedUuid, { type: 'list', attrs: previousNestedEntry?.attrs ? cloneListAttrs(previousNestedEntry.attrs, false) : cloneListAttrs(originalNested.attrs || {}, false), element: nestedList }); tracker.domMap.set(nestedList, nestedUuid); // Recurse into nested list this.processListRecursive( tracker, nestedList, originalNested.innerBlocks || [], previousEntries, previousRuntimeEntries ); } }); }, /** * Register one list or list-item runtime identity on the tracker. * * @param {Object} tracker Active list tracker. * @param {Element} element Live list or list-item element. * @param {string} type Supported runtime identity type. * @param {Map} previousRuntimeEntries Previous runtime entry map. * @returns {string} Resolved runtime UUID. */ registerRuntimeIdentity( tracker, element, type, previousRuntimeEntries = new Map() ) { const runtimeUuid = this.getOrCreateRuntimeUuid( tracker, element, type, previousRuntimeEntries ); if (!runtimeUuid) { return ''; } tracker.runtimeUuidMap.set(runtimeUuid, { type, element, }); tracker.runtimeDomMap.set(element, runtimeUuid); return runtimeUuid; }, /** * Return whether one runtime UUID is already owned by a different element. * * Native contenteditable list splitting can clone DOM attributes from the * source item into the newly created sibling. When that happens, the new * runtime UUID must be reseeded so each live cursor target stays unique. * * @param {Object} tracker Active list tracker. * @param {string} runtimeUuid Candidate runtime UUID. * @param {Element|null} element Element requesting that UUID. * @returns {boolean} True when the UUID belongs elsewhere. */ isRuntimeUuidClaimedByDifferentElement(tracker, runtimeUuid, element) { const normalizedRuntimeUuid = normalizeRuntimeUuid(runtimeUuid); if (!tracker?.runtimeUuidMap || !normalizedRuntimeUuid) { return false; } const existingEntry = tracker.runtimeUuidMap.get(normalizedRuntimeUuid); return !!(existingEntry?.element && existingEntry.element !== element); }, /** * Get the inherited structural ID for an element or seed it from the * runtime UUID when it does not exist yet. * * Structural IDs may be intentionally copied by native list splitting so * related items can retain style inheritance. They are not treated as * unique runtime cursor identifiers. */ getOrCreateUuid(element, type) { const attrName = type === 'list' ? LIST_ID_ATTR : LIST_ITEM_ID_ATTR; let uuid = element.getAttribute(attrName); if (!uuid) { uuid = normalizeRuntimeUuid( element.getAttribute( type === 'list' ? LIST_RUNTIME_UUID_ATTR : LIST_ITEM_RUNTIME_UUID_ATTR ) ) || this.generateTempUuid(); element.setAttribute(attrName, uuid); } return uuid; }, /** * Return the existing runtime UUID for one element or create one. * * Caller-supplied runtime UUIDs win for newly created items/lists. When an * element already belongs to the previous tracker build, its existing * runtime UUID is preserved so API references remain stable across rebuilds. * * @param {Object} tracker Active list tracker. * @param {Element} element Live list or list-item element. * @param {string} type Supported runtime identity type. * @param {Map} previousRuntimeEntries Previous runtime entry map. * @returns {string} Session-scoped runtime UUID. */ getOrCreateRuntimeUuid( tracker, element, type, previousRuntimeEntries = new Map() ) { const attrName = getRuntimeUuidAttributeName(type); if (!attrName || !isRuntimeIdentityElement(element, type)) { return ''; } let runtimeUuid = normalizeRuntimeUuid(element.getAttribute(attrName)); if (!runtimeUuid) { for (const [candidateUuid, entry] of previousRuntimeEntries.entries()) { if (entry?.element === element && entry.type === type) { runtimeUuid = normalizeRuntimeUuid(candidateUuid); break; } } } if (this.isRuntimeUuidClaimedByDifferentElement(tracker, runtimeUuid, element)) { runtimeUuid = ''; } if (!runtimeUuid) { runtimeUuid = this.generateTempUuid(); } element.setAttribute(attrName, runtimeUuid); return runtimeUuid; }, /** * Generate a RFC4122 version 4 UUID */ generateTempUuid() { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { const r = Math.random() * 16 | 0; const v = c === 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); }, /** * Return the zero-based tree path for one list item inside the tracked list. * * @param {Object} tracker Active list tracker. * @param {HTMLLIElement} listItem Candidate list item. * @returns {number[]|null} Zero-based indexes, or null on failure. */ getPathIndexesForItem(tracker, listItem) { if ( !tracker?.listElement || !listItem || listItem.nodeType !== Node.ELEMENT_NODE || listItem.tagName !== 'LI' || !tracker.listElement.contains(listItem) ) { return null; } const indexes = []; let currentItem = listItem; while (currentItem && tracker.listElement.contains(currentItem)) { const parentList = currentItem.parentElement; if (!parentList || (parentList.tagName !== 'UL' && parentList.tagName !== 'OL')) { return null; } const siblingItems = getDirectListItems(parentList); const itemIndex = siblingItems.indexOf(currentItem); if (itemIndex < 0) { return null; } indexes.unshift(itemIndex); const nextItem = parentList.closest('li'); if (!nextItem || !tracker.listElement.contains(nextItem)) { break; } currentItem = nextItem; } return indexes.length ? indexes : null; }, /** * Return the direct list item currently living at one tree path. * * @param {Object} tracker Active list tracker. * @param {string|Array} pathValue Zero-based path value. * @returns {HTMLLIElement|null} Matching list item. */ getItemByPath(tracker, pathValue) { const indexes = normalizePathIndexes(pathValue); if (!tracker?.listElement || !Array.isArray(indexes) || !indexes.length) { return null; } let currentList = tracker.listElement; let currentItem = null; for (let index = 0; index < indexes.length; index++) { const itemIndex = indexes[index]; const siblingItems = getDirectListItems(currentList); currentItem = siblingItems[itemIndex] || null; if (!currentItem) { return null; } if (index === indexes.length - 1) { return currentItem; } currentList = getDirectChildList(currentItem); if (!currentList) { return null; } } return currentItem; }, /** * Resolve one list element back to its public list path. * * The root list is addressed as an empty string. Nested child lists are * addressed by the path of the parent list item that owns them. * * @param {Object|null} tracker Active list tracker. * @param {HTMLElement|null} listElement Candidate list element. * @returns {string} Root-relative list path. */ getPathForList(tracker, listElement) { if (!tracker) tracker = this.active; if (!tracker?.listElement || !listElement || listElement.nodeType !== Node.ELEMENT_NODE) { return ''; } if (listElement === tracker.listElement) { return ''; } const parentItem = ( listElement.parentElement && listElement.parentElement.tagName === 'LI' ) ? listElement.parentElement : null; if (!parentItem) { return ''; } const indexes = this.getPathIndexesForItem(tracker, parentItem); return Array.isArray(indexes) ? indexes.join('_') : ''; }, /** * Return one tracked list item by runtime UUID. * * @param {Object|null} tracker Active list tracker. * @param {string} runtimeUuid Session-scoped item UUID. * @returns {HTMLLIElement|null} Matching list item. */ getItemByRuntimeUuid(tracker, runtimeUuid) { if (!tracker) tracker = this.active; const element = findElementByRuntimeUuid(tracker, runtimeUuid, 'item'); return element && element.tagName === 'LI' ? element : null; }, /** * Return one tracked list by runtime UUID. * * @param {Object|null} tracker Active list tracker. * @param {string} runtimeUuid Session-scoped list UUID. * @returns {HTMLElement|null} Matching list element. */ getListByRuntimeUuid(tracker, runtimeUuid) { if (!tracker) tracker = this.active; const element = findElementByRuntimeUuid(tracker, runtimeUuid, 'list'); return isListElementForRuntimeLookup(element) ? element : null; }, /** * Return the runtime UUID for one tracked list item. * * @param {Object|null} tracker Active list tracker. * @param {HTMLLIElement} listItem Live list item. * @returns {string} Session-scoped item UUID. */ getRuntimeUuidForItem(tracker, listItem) { if (!tracker) tracker = this.active; if (!tracker?.listElement || !listItem || listItem.tagName !== 'LI') { return ''; } return normalizeRuntimeUuid( listItem.getAttribute(LIST_ITEM_RUNTIME_UUID_ATTR) || tracker.runtimeDomMap?.get(listItem) ); }, /** * Return the runtime UUID for one tracked list. * * @param {Object|null} tracker Active list tracker. * @param {HTMLElement} listElement Live list element. * @returns {string} Session-scoped list UUID. */ getRuntimeUuidForList(tracker, listElement) { if (!tracker) tracker = this.active; if (!tracker?.listElement || !isListElementForRuntimeLookup(listElement)) { return ''; } return normalizeRuntimeUuid( listElement.getAttribute(LIST_RUNTIME_UUID_ATTR) || tracker.runtimeDomMap?.get(listElement) ); }, /** * Parse current DOM list structure to WordPress block format * Uses UUID to preserve original attrs, updates only content and ordered * * @param {HTMLElement} listElement Live UL/OL element. * @param {Object} tracker Active list tracker. * @param {boolean} allowUuidOwnership True only for the outermost * core/list. Nested lists must * serialize without persisted * plugin UUID attrs. */ parseListToBlock(listElement, tracker, allowUuidOwnership = true) { const isOrdered = listElement.tagName === 'OL'; const uuid = listElement.getAttribute(LIST_ID_ATTR); let attrs = { ordered: isOrdered }; if (uuid && tracker && tracker.uuidMap.has(uuid)) { const original = tracker.uuidMap.get(uuid); const originalAttrs = (Array.isArray(original.attrs) || !original.attrs) ? {} : cloneListAttrs(original.attrs, allowUuidOwnership); attrs = { ...originalAttrs, ordered: isOrdered }; } const listItems = Array.from(listElement.children).filter(el => el.tagName === 'LI'); const innerBlocks = listItems.map(li => this.parseListItemToBlock(li, tracker)); return createBlock('core/list', attrs, innerBlocks); }, /** * Parse a list item to WordPress block format * Preserves original attrs if UUID exists */ parseListItemToBlock(liElement, tracker) { const uuid = liElement.getAttribute(LIST_ITEM_ID_ATTR); let attrs = {}; if (uuid && tracker && tracker.uuidMap.has(uuid)) { const original = tracker.uuidMap.get(uuid); attrs = JSON.parse(JSON.stringify(original.attrs || {})); } // Recurse into nested list using the live element before cloning const innerBlocks = []; const nestedListEl = liElement.querySelector(':scope > ul, :scope > ol'); if (nestedListEl) { innerBlocks.push(this.parseListToBlock(nestedListEl, tracker, false)); } const clone = liElement.cloneNode(true); const directSurface = clone.querySelector(`:scope > [${LIST_ITEM_TEXT_ATTR}="1"]`); let content = ''; if (directSurface) { const cleanedSurface = this.cleanElement(directSurface); content = cleanedSurface.innerHTML.trim(); } else { const nestedInClone = clone.querySelector(':scope > ul, :scope > ol'); if (nestedInClone) nestedInClone.remove(); const cleaned = this.cleanElement(clone); content = cleaned.innerHTML.trim(); } return createBlock('core/list-item', { ...attrs, content }, innerBlocks); }, /** * Clean element using ElementPrep * Removes editing artifacts while preserving UUIDs and content */ cleanElement(element) { if (SFE.ElementPrep) { return SFE.ElementPrep.clean(element, { removeIdentity: true, removeControls: true, clone: false // Already working with a clone }); } console.error('ElementPrep not found'); return element; }, /** * Serialize the current list structure to WordPress block format * Preserves all original attrs, updates only content and ordered */ serialize(tracker) { if (!tracker) tracker = this.active; if (!tracker) return null; const block = this.parseListToBlock(tracker.listElement, tracker); return serializeBlocks([block]); }, /** * Update the tracker's element reference * Used when the list element is replaced (e.g., OL to UL conversion) */ updateElement(tracker, newElement) { if (!tracker) tracker = this.active; if (!tracker) return; // Ensure new element keeps the tracker reference newElement._mwpListTracker = tracker; this.syncEditableTextSurfaces(newElement); tracker.listElement = newElement; // Update element references in uuidMap tracker.uuidMap.forEach((value, key) => { if (value.type === 'list') { const newEl = newElement.querySelector(`[${LIST_ID_ATTR}="${key}"]`) || (newElement.getAttribute(LIST_ID_ATTR) === key ? newElement : null); if (newEl) { value.element = newEl; } } else if (value.type === 'item') { const newEl = newElement.querySelector(`[${LIST_ITEM_ID_ATTR}="${key}"]`); if (newEl) { value.element = newEl; } } }); tracker.runtimeUuidMap.forEach((value, key) => { if (value.type === 'list') { const newEl = this.getListByRuntimeUuid(tracker, key); if (newEl) { value.element = newEl; tracker.runtimeDomMap.set(newEl, key); } } else if (value.type === 'item') { const newEl = this.getItemByRuntimeUuid(tracker, key); if (newEl) { value.element = newEl; tracker.runtimeDomMap.set(newEl, key); } } }); }, /** * Return one tree-aware structural snapshot for the active list. * * @param {Object|null} tracker Active list tracker. * @returns {Object|null} Lightweight structure snapshot. */ getStructure(tracker) { if (!tracker) tracker = this.active; if (!tracker?.listElement) { return null; } const buildListNode = (listElement, listPath = '', parentIndexes = []) => ({ listUuid: this.getRuntimeUuidForList(tracker, listElement), listPath, ordered: listElement.tagName === 'OL', items: getDirectListItems(listElement).map((listItem, index) => { const indexes = [ ...parentIndexes, index ]; const pathMeta = buildPathMeta(indexes); const directSurface = ensureDirectItemTextSurface(listItem); const nestedList = getDirectChildList(listItem); return { itemUuid: this.getRuntimeUuidForItem(tracker, listItem), path: pathMeta.path, pathLabel: pathMeta.pathLabel, depth: pathMeta.depth, contentHtml: directSurface ? directSurface.innerHTML.trim() : '', childList: nestedList ? buildListNode(nestedList, pathMeta.path, indexes) : null, }; }), }); return buildListNode(tracker.listElement, '', []); }, /** * Apply one primitive structural list operation against the live tracked * DOM. * * Public API calls are translated into this lower-level operation set by * the shared schema executor so the tracker only needs to understand the * canonical primitive mutation layer. * * Supported primitive kinds: * - `insert_list_item` * - `remove_list_item` * - `move_list_item` * - `indent_list_item` * - `outdent_list_item` * - `update_list_item_text` * - `toggle_list_type` * * @param {Object|null} tracker Active list tracker. * @param {Object} rawOperation Structural operation payload. * @returns {Object|null} Result summary when applied. */ applyOperation(tracker, rawOperation, options = {}) { if (!tracker) tracker = this.active; if (!tracker?.listElement || !rawOperation || typeof rawOperation !== 'object') { return null; } const kind = typeof rawOperation.kind === 'string' ? rawOperation.kind.trim().toLowerCase() : ''; const path = rawOperation.path ?? rawOperation.itemPath ?? rawOperation.item_path ?? ''; const listItem = this.getItemByPath(tracker, path); let didApply = false; if (kind === 'remove_list_item' && listItem) { didApply = applyRemoveListItemOperation(tracker, listItem); } else if (kind === 'indent_list_item' && listItem) { didApply = applyIndentListItemOperation(tracker, listItem); } else if (kind === 'outdent_list_item' && listItem) { didApply = applyOutdentListItemOperation(tracker, listItem); } else if (kind === 'toggle_list_type') { didApply = applyToggleListTypeOperation(tracker, rawOperation, options); } else if (kind === 'update_list_item_text' && listItem) { didApply = applyUpdateListItemTextOperation(listItem, rawOperation); } else if (kind === 'insert_list_item') { didApply = applyInsertListItemOperation(tracker, rawOperation, this); } else if (kind === 'move_list_item' && listItem) { didApply = applyMoveListItemOperation(tracker, listItem, rawOperation, this); } if (!didApply) { return null; } this.syncEditableTextSurfaces(tracker.listElement); this.buildFromDOM(tracker, tracker.listElement, tracker.originalBlock || {}); return { kind, structure: this.getStructure(tracker), }; }, /** * Ensure every list item in one tree has one direct text surface. * * @param {HTMLElement|null} rootElement Candidate list root or list item. * @returns {HTMLElement|null} Normalized root element. */ syncEditableTextSurfaces(rootElement) { if (!rootElement || rootElement.nodeType !== Node.ELEMENT_NODE) { return null; } if (rootElement.tagName === 'LI') { ensureDirectItemTextSurface(rootElement); Array.from(rootElement.children || []) .filter(child => isNestedListNode(child)) .forEach(childList => this.syncEditableTextSurfaces(childList)); return rootElement; } if (rootElement.tagName !== 'UL' && rootElement.tagName !== 'OL') { return rootElement; } Array.from(rootElement.children || []) .filter(child => child.tagName === 'LI') .forEach(li => this.syncEditableTextSurfaces(li)); return rootElement; }, /** * Ensure runtime UUID attributes are unique within one live list tree. * * Native contenteditable list splitting can clone `li` and nested list * attributes verbatim before FrontEdit regains control. This pass reseeds only the * session-scoped runtime UUID attributes so freshly created structures become * distinct live API/editor targets immediately, while leaving `data-item-id` * and `data-list-id` untouched for their separate responsibilities. * * @param {HTMLElement|null} rootElement Candidate list root. * @returns {HTMLElement|null} Normalized root element. */ ensureUniqueRuntimeUuids(rootElement) { if ( !rootElement || rootElement.nodeType !== Node.ELEMENT_NODE || (rootElement.tagName !== 'UL' && rootElement.tagName !== 'OL') ) { return rootElement || null; } const seenListRuntimeUuids = new Set(); const seenItemRuntimeUuids = new Set(); const collectLists = [ rootElement, ...Array.from(rootElement.querySelectorAll('ul, ol')) ]; const collectItems = Array.from(rootElement.querySelectorAll('li')); collectLists.forEach(listElement => { const runtimeUuid = normalizeRuntimeUuid( listElement.getAttribute(LIST_RUNTIME_UUID_ATTR) ); if (!runtimeUuid) { return; } if (seenListRuntimeUuids.has(runtimeUuid)) { listElement.setAttribute(LIST_RUNTIME_UUID_ATTR, this.generateTempUuid()); return; } seenListRuntimeUuids.add(runtimeUuid); }); collectItems.forEach(listItem => { const runtimeUuid = normalizeRuntimeUuid( listItem.getAttribute(LIST_ITEM_RUNTIME_UUID_ATTR) ); if (!runtimeUuid) { return; } if (seenItemRuntimeUuids.has(runtimeUuid)) { listItem.setAttribute(LIST_ITEM_RUNTIME_UUID_ATTR, this.generateTempUuid()); return; } seenItemRuntimeUuids.add(runtimeUuid); }); return rootElement; }, /** * Ensure one live list tree has the required structural IDs and runtime UUIDs. * * On first editor activation, list elements may not yet have either identity * attribute family. Seed both from one generated UUID per element so the * initial history snapshot captures stable targeting data. Later native list * splits may intentionally copy the structural IDs; in those cases this pass * preserves the structural IDs and only reseeds duplicated runtime UUIDs. * * @param {HTMLElement|null} rootElement Candidate list root. * @returns {HTMLElement|null} Normalized root element. */ ensureIdentityAttributes(rootElement) { if ( !rootElement || rootElement.nodeType !== Node.ELEMENT_NODE || (rootElement.tagName !== 'UL' && rootElement.tagName !== 'OL') ) { return rootElement || null; } const syncIdentityPair = (element, structuralAttrName, runtimeAttrName) => { if (!element || element.nodeType !== Node.ELEMENT_NODE) { return; } let structuralId = normalizeRuntimeUuid( element.getAttribute(structuralAttrName) ); let runtimeUuid = normalizeRuntimeUuid( element.getAttribute(runtimeAttrName) ); if (!structuralId && !runtimeUuid) { runtimeUuid = this.generateTempUuid(); structuralId = runtimeUuid; } else if (!runtimeUuid) { runtimeUuid = structuralId; } else if (!structuralId) { structuralId = runtimeUuid; } element.setAttribute(structuralAttrName, structuralId); element.setAttribute(runtimeAttrName, runtimeUuid); }; syncIdentityPair(rootElement, LIST_ID_ATTR, LIST_RUNTIME_UUID_ATTR); Array.from(rootElement.querySelectorAll('ul, ol')).forEach(listElement => { syncIdentityPair(listElement, LIST_ID_ATTR, LIST_RUNTIME_UUID_ATTR); }); Array.from(rootElement.querySelectorAll('li')).forEach(listItem => { syncIdentityPair(listItem, LIST_ITEM_ID_ATTR, LIST_ITEM_RUNTIME_UUID_ATTR); }); return this.ensureUniqueRuntimeUuids(rootElement); }, getDirectItemTextSurface, normalizePathIndexes, buildPathMeta, getRuntimeUuidAttributeName, getListRuntimeUuidAttributeName() { return LIST_RUNTIME_UUID_ATTR; }, getListItemRuntimeUuidAttributeName() { return LIST_ITEM_RUNTIME_UUID_ATTR; }, getListIdAttributeName() { return LIST_ID_ATTR; }, getListItemIdAttributeName() { return LIST_ITEM_ID_ATTR; }, /** * Destroy tracker and clean up */ destroy(tracker) { if (!tracker) tracker = this.active; if (!tracker) return; tracker.uuidMap.clear(); tracker.runtimeUuidMap.clear(); if (tracker.listElement) { delete tracker.listElement._mwpListTracker; } if (this.active === tracker) this.active = null; } }; /** * Return whether one element is a live list node for runtime lookup helpers. * * @param {Element|null} element Candidate DOM element. * @returns {boolean} True when the element is `UL` or `OL`. */ function isListElementForRuntimeLookup(element) { return !!( element && element.nodeType === Node.ELEMENT_NODE && (element.tagName === 'UL' || element.tagName === 'OL') ); } // Expose globally SFE.ListBlockTracker = ListBlockTracker; })(); public Archives - Page 18 of 23 - Luxe Designs

Dive into the world of Best Online Pokies Australia: Real money slots and quick



Australia’s vibrant online casino scene continues to grow, offering players an exciting mix of real money pokies. With an array of games, fast withdrawals, and enticing bonuses, exploring these digital establishments can be an exhilarating experience. As of 2026, players can find the best licensed online pokies sites in Australia that promise secure payments and effortless gameplay, including options like the best australian online casino which enhances the overall gaming experience. Let’s dive into this thrilling realm where chance meets opportunity, and discover what makes online pokies a favorite among gamers in Australia.

A clear starting point for casino

Online casinos have transformed the way players engage with their favorite games. With the advent of technology, accessing a wide selection of pokies has never been easier. Australian online pokies sites are designed to cater to both seasoned players and newcomers, offering a seamless blend of entertainment and potential rewards. Players can immerse themselves in themes that range from adventure to mythology, all while enjoying the thrill of potential payouts. In essence, this is not just about spinning reels; it is about creating an engaging experience that resonates with players.

Many online casinos ensure they are licensed and regulated, providing a sense of safety and trust. As you engage with various platforms, it’s important to look out for those that offer favorable terms, including substantial welcome bonuses and reliable payment options like PayID and Neosurf. This enhances the overall gaming experience while ensuring players feel secure while they enjoy their favorite slots.

How to get started with online pokies

Setting up your journey in the world of online pokies is straightforward, allowing you to focus on gameplay rather than technicalities. Here’s a step-by-step guide to help you get started:

  1. Create an Account: Registering with a licensed online casino is your first step. Fill out the required details and agree to the terms.
  2. Verify Your Details: Most platforms will ask for verification. This ensures that your information is secure and helps in smooth withdrawals.
  3. Make a Deposit: Choose from various payment methods, including PayID or Neosurf, to fund your account comfortably.
  4. Select Your Game: Browse through a wide selection of pokies and choose one that aligns with your preferences.
  5. Start Playing: Place your bets and enjoy spinning the reels. Always remember to gamble responsibly.
  • Easy registration process with quick account setup.
  • Variety of payment options for seamless transactions.
  • Instant access to a plethora of pokies once your account is funded.

Practical details for enjoying online pokies

Engaging with online pokies in Australia not only provides entertainment but also comes with various benefits. One such advantage is the impressive range of welcome bonuses available in 2026. New players often encounter enticing offers, such as a 450% bonus up to 5,000 AUD and additional free spins. Such promotions are designed to boost your bankroll, allowing for more extended gameplay and increased chances of winning. Furthermore, the withdrawal processes are swift, with options for cryptocurrency allowing you to access your winnings within 12 to 24 hours. Card withdrawals typically take between 1 to 3 days, ensuring that your money is easily accessible.

  • Significant welcome bonuses that enhance your initial deposit.
  • Fast withdrawal options ensuring you get your winnings quickly.
  • A wide range of games, appealing to diverse player preferences.

The convenience of mobile compatibility is another key feature of online pokies. Most platforms are optimized for mobile devices, meaning you can enjoy your favorite games from anywhere, whether at home or on the go. Ease of access makes the experience even more enjoyable, focusing on the excitement of gaming rather than logistical challenges.

Key benefits of playing online pokies

Exploring the world of online pokies brings multiple advantages that enhance the gaming experience. Here are some notable benefits that players can enjoy:

  • Variety of Games: Online casinos offer hundreds of different pokies, ensuring players never run out of options to explore.
  • Bonuses and Promotions: Continuous promotions such as reload bonuses, free spins, and loyalty rewards keep players engaged and motivated.
  • Convenience: The ability to play from anywhere at any time without needing to travel to a physical casino adds to the appeal.
  • Secure Transactions: Licensed casinos prioritize security, providing peace of mind regarding your financial transactions.

These benefits contribute to a rich environment that encourages both fun and strategic play, making online pokies an attractive option for many Australians.

Trust and security in online gaming

When engaging with online casinos, security is paramount. Reputable Australian online pokies sites employ advanced encryption technology to safeguard player information and financial transactions. This safeguarding ensures that personal data remains confidential and protected from potential threats. Additionally, choosing licensed casinos means that these platforms adhere to strict regulatory standards, providing a layer of trustworthiness.

Moreover, established casinos offer responsible gambling features, allowing players to set limits on their deposits and playtime. This not only promotes a healthy gaming environment but also ensures that players enjoy their experience without taking unnecessary risks.

  • Advanced encryption technology to protect your data.
  • Regulated platforms for reliable gaming experiences.
  • Responsible gambling options to encourage healthy gaming habits.

Why choose the top online pokies sites

Choosing the right online pokies site can significantly enhance your gaming experience. The best platforms combine reliability with user-friendly interfaces, diverse game selections, and lucrative bonuses. In 2026, it’s crucial to opt for licensed sites that not only offer security but also provide a thrilling gaming atmosphere. This ensures that as you explore different pokies, you can do so confidently, knowing that your experience is catered to your preferences.

In summary, the world of online pokies in Australia presents an engaging and rewarding experience for players. With continuous advancements in technology and an expansive range of games and bonuses, there has never been a better time to dive into this exciting domain. Make informed choices, enjoy the journey, and embrace the thrill of potential winnings as you spin the reels in 2026.

Neosurf Casino Australia: your gateway to the best online pokies and secure banking options



In the vibrant world of online gaming, Neosurf casinos in Australia play a pivotal role by providing players with secure banking options and an extensive selection of online pokies. As the popularity of online gambling grows, Australian players seek platforms that not only offer exciting gaming experiences but also prioritize safety and ease of use, such as australia online casino , which have emerged as reliable options, allowing players to enjoy their favorite games while ensuring their financial transactions are quick and secure.

How new players can read the key casino signals

Understanding the landscape of online casinos is crucial for both newcomers and seasoned players alike. When exploring Neosurf casinos, players should look for specific signals indicating a trustworthy gaming environment. This includes checking the site’s licensing information, game variety, payment options, and customer support availability. Being informed about these key aspects ensures a satisfying and secure gaming experience while maximizing the potential for winnings.

Moreover, it’s essential to pay attention to the various promotions offered, such as welcome bonuses and free spins, which can significantly enhance the initial gaming experience. By choosing a reputable Neosurf casino, players can dive into a world of exciting online pokies while knowing their deposits are handled securely.

How to get started with Neosurf casinos

Getting started at a Neosurf casino is straightforward and user-friendly. Below is a step-by-step guide to help players enjoy their online gaming experience:

  1. Create an Account: Register on the casino site by providing your details, which typically include your name, email address, and date of birth.
  2. Verify Your Details: Complete the verification process to ensure your account is secure and compliant with regulations.
  3. Make a Deposit: Choose Neosurf as your preferred payment method, with minimum deposits starting as low as 10 AUD.
  4. Select Your Game: Browse through a diverse selection of online pokies and other games available at the casino.
  5. Start Playing: Dive into your chosen game and enjoy the thrill of online gambling.
  • Simple account creation process for convenience
  • Fast and secure deposits using Neosurf
  • Wide variety of games to ensure every player finds something they enjoy

Practical details for enjoying online pokies

Neosurf casinos in Australia offer an extensive range of online pokies, making them incredibly appealing to players. Featuring various themes, designs, and game mechanics, these pokies provide hours of entertainment. Players can enjoy popular titles that boast exciting graphics and engaging gameplay. Additionally, many casinos offer exclusive bonuses, such as up to 3,000 AUD plus 200 free spins, making it all the more tempting to join.

Players can also benefit from a fast mobile cashier, allowing them to manage their funds seamlessly from their mobile devices. The flexibility of mobile compatibility means that you can enjoy your favorite games on the go, without sacrificing quality. With the right Neosurf casino, players can immerse themselves in a truly dynamic gaming environment.

  • A wide variety of online pokies, including classic and modern options
  • Exclusive promotions and generous welcome bonuses to boost your initial gaming funds
  • Seamless mobile experience for gaming anytime, anywhere

With so many enticing options and features available, it’s clear why Neosurf casinos are a popular choice for players in Australia.

Key benefits of choosing Neosurf casinos

Opting for Neosurf as a payment method at online casinos comes with numerous advantages. Firstly, it enhances the security of online transactions, as players do not need to share sensitive banking information directly with the casino. Secondly, Neosurf transactions are processed quickly, allowing for instant deposits, which means players can start gaming right away.

  • High level of security to protect player information
  • Quick transaction processing for immediate gameplay
  • User-friendly experience for both new and experienced players
  • Access to exclusive bonuses that can increase your playtime

These benefits make Neosurf casinos a preferred choice for many Australian players seeking both security and excitement.

Trust and security in Neosurf casinos

One of the foremost concerns for players at online casinos is trust and security. Neosurf casinos prioritize player safety by employing advanced encryption technologies, ensuring that all transactions are protected from unauthorized access. Additionally, reputable casinos are licensed by recognized regulatory authorities, further enhancing their credibility. Players can feel confident knowing that their funds and personal data are secure while enjoying their favorite online games.

Moreover, Neosurf’s payment method does not require personal bank details, which adds an extra layer of security. This anonymity appeals to many players who prefer to keep their financial transactions discreet.

Why choose Neosurf casinos in Australia

Choosing a Neosurf casino provides Australian players with an exciting and secure online gaming experience. With the ability to make straightforward deposits and access a myriad of online pokies, players can enjoy a seamless gaming session. The attractive bonuses and promotions offered by these casinos only enhance the overall experience, making it an enjoyable venture.

In conclusion, Neosurf casinos stand out for their commitment to player security, convenience in payment, and an expansive selection of games. Whether you’re a new player looking to explore or an experienced gambler seeking a trustworthy platform, Neosurf casinos in Australia offer everything you need for an engaging and rewarding gaming experience.

Best Online Casino South Africa 2026: your ultimate resource for promotions and game selection



The online casino landscape in South Africa has blossomed, providing players with a multitude of options for exhilarating gaming experiences. In 2026, choosing the right online casino is crucial for enjoying a safe, secure, and rewarding gaming experience, especially when you consider the best online casino options available that cater to various preferences. This article serves as your ultimate resource, highlighting the best features, promotions, and game selections offered by top online casinos in South Africa. Whether you are a new player or a seasoned gambler, understanding the landscape will help you maximize your enjoyment.

How the core features support everyday play

Navigating the world of online casinos can be overwhelming, especially with the plethora of choices available. Key features that enhance everyday play include game selection, promotions, and payment options. A good online casino offers a wide variety of games, including premium slots, table games, and live dealer options, creating a vibrant gaming atmosphere. Furthermore, attractive promotions such as welcome bonuses and free spins encourage engagement and can significantly boost your bankroll.

With fast withdrawals and diverse payment options, players can enjoy a seamless experience when managing their funds. Online casinos that support ZAR payment methods make it easier for South African players to deposit and withdraw money conveniently. Understanding these core features can lead to a more enjoyable and productive gaming experience.

How to get started

Getting started with an online casino in South Africa can be a simple process if you follow the right steps. Here’s a detailed guide to help you get going:

  1. Create an Account: Visit the casino’s website and complete the registration form with your details.
  2. Verify Your Details: Confirm your identity by providing necessary documentation, enhancing security.
  3. Make a Deposit: Choose your preferred payment method, whether it’s an OTT Voucher or Ozow Instant EFT.
  4. Select Your Game: Explore a range of games, including the latest slots and classic table games.
  5. Start Playing: Enjoy your gaming experience, taking advantage of promotions and bonuses available.
  • Quick and easy account creation ensures you can start playing sooner.
  • Verification enhances your security and protects your account.
  • Diverse payment options cater to various player preferences.

Practical details for online casinos in South Africa

Today’s online casinos deliver a rich lineup of features tailored for South African players. For instance, many platforms offer a welcome bonus that can go as high as R5,000, plus additional perks like free bets and free spins. Additionally, the variety of games is impressive, with over 1,000 premium slots available for players to enjoy. This diverse selection caters to different tastes, ensuring everyone finds something appealing.

Besides extensive gameplay options, casinos focus on fast withdrawal processes. Players appreciate the ability to access their winnings swiftly, which enhances trust and satisfaction. Coupled with secure ZAR payment methods, managing your funds becomes both convenient and safe, allowing you to focus on the gaming experience itself.

  • Welcoming bonuses enhance initial deposits, boosting your starting balance.
  • Over 1,000 games provide a wide range of options for all players.
  • Fast withdrawals ensure quick access to your winnings.

With these practical aspects in mind, you can better appreciate the clear advantages of engaging with online casinos in South Africa.

Key benefits of online casinos

Choosing to play at an online casino comes with numerous benefits that significantly enrich your gaming experience. Among these perks are the accessibility of games and the convenience of play from home. Players can log in at any time, enjoying their favorite games without the need for travel.

  • Accessibility: Play anytime, anywhere, as long as you have an internet connection.
  • Game Variety: A vast selection of games caters to diverse preferences and interests.
  • Promotions: Regular bonuses and promotions keep the gaming experience exciting and rewarding.
  • 24/7 Availability: Many casinos operate around the clock, ensuring you can play whenever you want.

These advantages make online gaming an increasingly popular choice for players across South Africa, fostering a thriving gaming community.

Trust and security

Trust and security are paramount in the online gaming industry. Leading online casinos are licensed and regulated, ensuring they adhere to strict standards that protect players. Reputable casinos utilize advanced encryption technology to safeguard sensitive information and financial transactions, providing players with peace of mind.

Moreover, responsible gaming measures are in place to ensure a safe environment. This includes options for self-exclusion and limits on deposits and playtime, allowing players to manage their gaming responsibly. Choosing a casino that prioritizes security can greatly enhance your overall gaming experience.

  • Licensed and regulated casinos ensure compliance with industry standards.
  • Encryption technology protects personal and financial data.
  • Responsible gaming tools help players maintain control over their play.

Why choose an online casino in South Africa

Selecting an online casino in South Africa comes with distinct advantages tailored to local players. With competitive bonuses, an extensive game catalog, and user-friendly payment options, you can find a platform that aligns with your gaming needs. Additionally, the focus on customer support ensures that you have assistance whenever required, enhancing your overall experience.

In summary, the vibrant online casino scene in South Africa in 2026 offers players an array of choices. By focusing on trustworthy operators with beneficial features, you can enjoy a thrilling and rewarding gaming journey. So get started today, and experience the excitement and potential rewards that come with playing online!

Beste casinospillene du ikke må gå glipp av

Beste casinospillene du ikke må gå glipp av

Populære spilleautomater

Spilleautomater har alltid vært en av de mest populære casinospillene, både på nett og i fysiske kasinoer. Med spennende temaer, fantastiske grafikker og varierte funksjoner, tiltrekker de spillere i alle aldre. Det er mulig å finne beste casino bonus for norske spillere blant de nyeste tilbudene. Noen av de mest kjente spilleautomatene inkluderer klassikere som “Starburst” og “Gonzo’s Quest”, som tilbyr både underholdning og muligheter for store gevinster.

I tillegg til de tradisjonelle spilleautomatene, har utviklere skapt innovative videoautomater med unike funksjoner som bonusspill og gratisspinn. Disse spillene gir en spennende opplevelse som er vanskelig å motstå, og kan virkelig løfte spillopplevelsen til nye høyder.

Kortspill med dyktighet

Kortspill som blackjack og poker krever mer enn bare flaks; de krever strategi og ferdigheter. Blackjack er et av de mest spilte kortspillene på nettet, hvor målet er å slå dealeren ved å få en hånd som er nærmere 21 enn dealerens hånd, uten å gå over. Mange spillere setter pris på strategien som går inn i dette spillet, og det finnes mange varianter som holder spillopplevelsen fersk og spennende. For de som liker å utfordre seg selv, er ansvarsfull spilling viktig, slik at man kan nyte spillet.

Poker, på den annen side, er et spill som kombinerer ferdigheter og psykologi. Det finnes mange varianter av poker, fra Texas Hold’em til Omaha, og hver variant har sine egne regler og strategier. Spillere kan delta i både cash games og turneringer, noe som gir mange muligheter for både nybegynnere og erfarne spillere.

Live kasinoopplevelser

Live kasinoer har revolusjonert måten vi spiller på nettet. Ved å bruke avansert teknologi kan spillere nå delta i ekte casinospill fra komforten av sitt eget hjem. Spill som live roulette, live blackjack og live baccarat gir en autentisk opplevelse med ekte dealere som interagerer med spillerne i sanntid.

Denne typen spilling har blitt svært populær, da den kombinerer bekvemmeligheten av online spilling med den sosiale interaksjonen man finner i fysiske kasinoer. Det gir også muligheten for spillere å kommunisere med dealeren og andre spillere, noe som forbedrer opplevelsen ytterligere.

Variasjoner av bordspill

Bordspill har alltid vært en favoritt blant casinospillere. I tillegg til klassikere som roulette og craps, finnes det mange spennende variasjoner som gir nye perspektiver på tradisjonelle spill. For eksempel, fransk roulette gir unike innsatser og regler som kan være mer fordelaktige for spilleren.

Dessuten, det finnes mange innovasjoner innen bordspill som kombinerer forskjellige elementer fra flere spill. Disse variasjonene kan gjøre spillopplevelsen mer dynamisk og underholdende, noe som er perfekt for de som ønsker å prøve noe nytt og spennende.

Informasjon om nettstedet vårt

Vår hjemmeside er dedikert til å hjelpe spillere med å finne de beste casinoene og spillene tilgjengelig i Norge. Vi tilbyr grundige vurderinger av ulike nettcasinoer, basert på kriterier som spillutvalg, bonuser, betalingsmetoder og sikkerhet. Vårt mål er å gi deg en trygg og morsom spillopplevelse.

I tillegg gir vi nyttige tips og veiledning for ansvarlig spilling, slik at du kan nyte spillene på en trygg måte. Enten du er en nybegynner eller en erfaren spiller, er våre ressurser designet for å forbedre din casinoupplevelse. Besøk oss for å lære mer og bli en del av vår spillfellesskap!

Experience live sports betting at best online casinos.ca: The best games and promotions to



For sports enthusiasts and casino lovers alike, live sports betting offers an exhilarating way to engage with your favorite games. Online casinos have revolutionized the gambling experience by providing players with the ability to place bets in real-time while enjoying a rich assortment of games and promotions. Engaging in live sports betting not only elevates the excitement of watching sports but also presents significant opportunities for strategic gameplay, especially when considering the best sports betting sites canada that complement your casino experience. This article will unpack essential tips about live sports betting and highlight some of the best games and promotions available at trusted online casinos.

How account setup, payments, and play fit together

Setting up an account for live sports betting at online casinos is a straightforward process that paves the way for thrilling experiences. Understanding how account creation, payment methods, and game selection work together is crucial for a seamless betting experience. Players can benefit from various payment options like Interac, Visa, and cryptocurrencies, ensuring that their deposits and withdrawals are processed efficiently. Each casino may have different protocols, but the fundamental aspects remain fairly similar, designed to provide users with a user-friendly experience while ensuring security and accessibility.

Moreover, these online platforms often come equipped with intuitive interfaces that allow you to navigate easily between sports events, live dealer games, and promotional offers. This cohesiveness enhances the overall gaming experience and makes it easy for players to get into the action without unnecessary delays.

Getting started with live sports betting

The world of live sports betting can seem overwhelming initially, but it becomes manageable with a clear plan. Here are the essential steps to get started:

  1. Create an Account: Sign up on a trusted online casino platform to begin your journey. You’ll typically need to provide some personal information and choose a secure password.
  2. Verify Your Details: Most platforms will require verification of your identity and payment methods. This step is crucial for your security.
  3. Make a Deposit: Choose from a variety of payment methods like Visa or Bitcoin to fund your account. Make sure to check for any deposit bonuses that may be available.
  4. Select Your Game: Browse the sports events available for betting. Look for markets that pique your interest and offer the best odds.
  5. Start Betting: Place your bets! Take advantage of your knowledge and intuition to make informed decisions.
  • Easy account setup process to get you started quickly.
  • Variety of payment options cater to different preferences.
  • Immediate access to a wide range of sporting events for betting.

Practical details for engaging in live sports betting

When participating in live sports betting, several key details can enhance your experience. Many online sportsbooks offer live streaming options, allowing you to watch games as they unfold. This feature not only elevates the excitement but also enables you to make informed betting decisions based on real-time developments. Moreover, casinos often provide comprehensive statistics and live scores, making it easier for players to track their bets.

  • Live streaming of events for real-time engagement.
  • Access to updated statistics and scores that help inform your bets.
  • Promotional offers that refresh regularly to enhance your betting experience.

Additionally, familiarizing yourself with various betting types, such as point spreads, money lines, and over/under bets, allows you to diversify your betting strategy. By understanding these facets, you can tailor your betting approach to different sports and events, ultimately maximizing your potential payout.

Key benefits of sports betting at online casinos

Live sports betting through reputable online casinos comes with several advantages that can significantly enhance your gaming experience. Unlike traditional betting, online platforms provide players with unparalleled convenience, enabling them to place bets from the comfort of their homes or while on the go. This level of accessibility is further augmented by mobile-friendly interfaces designed for seamless betting on smartphones.

  • Convenient access to a wide variety of sports and betting markets.
  • Enhanced promotional offers that provide extra value.
  • Instant withdrawals available at select casinos, enhancing cash flow.
  • Real-time betting options that add excitement to every game.

Furthermore, many platforms now offer bonuses such as deposit matches and free spins, which serve as excellent opportunities for players to build their bankroll without risking personal funds. Engaging actively during promotions can lead to a more rewarding experience, encouraging users to explore new games and betting markets.

Trust and security in online sports betting

Trust and security are paramount when engaging in online sports betting. Leading casinos employ advanced encryption methods to protect your personal and financial information, ensuring that your transactions remain secure. Additionally, most reputable online casinos hold licenses from recognized authorities, such as Curaçao or Anjouan, which play a significant role in maintaining industry standards and enforcing fairness in gaming practices.

It is essential to choose platforms that have a solid reputation and provide transparency in their operations. Customer reviews and ratings can offer insight into the reliability and trustworthiness of the casino, giving you the assurance needed to place your bets with confidence.

Why choose online sports betting platforms

Online sports betting offers an engaging blend of excitement, strategy, and potential rewards. By maximizing the benefits of promotions, utilizing multiple payment methods, and enhancing your betting knowledge, you can significantly enhance your wagering experience. Choosing an online casino that prioritizes security, user satisfaction, and innovative features will only further your chances of enjoying a successful venture into live sports betting.

As you explore the various opportunities that online casinos offer, remember to prioritize responsible gaming practices, make informed decisions, and take full advantage of the promotions designed to enhance your playing experience. Happy betting!

New Online Casino Canada 2026: Dive into a world of thrilling games and promotions



The online casino landscape in Canada is rapidly evolving, especially as we step into 2026. With the introduction of new platforms offering an exciting array of games and generous promotions, players are spoiled for choice. This article explores the thrilling opportunities available at new online casinos, including enhanced security protocols, diverse payment methods, and the best online casino enticing welcome bonuses tailored for Canadian players.

How trust, access, and rewards connect in casino

Trust, access, and rewards are pivotal aspects of the online casino experience. A trustworthy casino offers verified licenses, ensuring that players can engage in gaming without concerns about fairness or security. With many new online casinos opening from July 2025 to March 2026, players can find ample options that emphasize these critical factors. Moreover, accessibility is enhanced through various payment methods such as Interac, Visa, and cryptocurrencies, making transactions seamless and efficient.

Additionally, rewards play a crucial role in drawing players to new online casinos. With welcome bonuses reaching up to C$5,000 plus 300 free spins, players can maximize their initial deposits while enjoying a broader game selection. This combination of trust, access, and rewards sets the stage for an exhilarating gaming experience tailored to meet the unique needs of Canadian players.

How to get started with new online casinos

Beginning your journey at a new online casino is an exciting endeavor, especially in the Canadian market of 2026. Here’s a step-by-step guide to ensure a smooth experience:

  1. Create an Account: Register by providing your details such as name, email, and address.
  2. Verify Your Details: Confirm your identity by submitting necessary documents as per the casino’s requirements.
  3. Make a Deposit: Choose your preferred payment method—options include Interac, credit cards, and cryptocurrencies.
  4. Select Your Game: Browse through the diverse game selection, including slots, table games, and live dealer options.
  5. Start Playing: Engage with your chosen game and enjoy the thrill of online gaming.
  • Seamlessly create an account for easy access
  • Quick verification ensures your security
  • Diverse payment options cater to all players

Practical details for new online casinos

The practical side of joining a new online casino includes understanding the features that enhance your gaming experience. New casinos in Canada often offer competitive welcome bonuses, which can significantly boost your starting bankroll. In 2026, players can expect bonuses that reach as high as C$5,000 along with hundreds of free spins, making it an ideal time to explore new platforms.

Furthermore, the average withdrawal time is impressively quick, with players generally receiving their funds within 20 minutes to 5 days, depending on the chosen payment method. This efficiency allows for a more enjoyable gaming experience, as players are not left waiting for extended periods. Plus, deposits can often be made in Canadian dollars, eliminating the hassle of currency conversion.

  • Up to C$5,000 in welcome bonuses
  • Fast payouts averaging 20 minutes to 5 days
  • Games available in Canadian dollars for convenience

With crucial details like these, players can make informed decisions and quickly adapt to the online casino environment.

Key benefits of exploring new online casinos

There are numerous compelling reasons to consider new online casinos in Canada. Many of these platforms are designed with user experience in mind, featuring intuitive interfaces and a plethora of gaming options. Additionally, the competitive nature of the industry drives these casinos to offer lucrative bonuses and promotions to attract players.

  • Access to a diverse game selection, including innovative titles
  • Exclusive promotions for new players, enhancing the welcome experience
  • Advanced security protocols ensuring safe gaming
  • Responsive customer support for prompt assistance

These benefits not only enhance the gaming experience but also provide players with peace of mind, knowing they are in a secure environment filled with exciting opportunities.

Trust and security in online casinos

Trust and security are paramount in choosing a reputable online casino. Most new casinos are licensed and regulated, ensuring they adhere to strict guidelines that protect players. This includes using advanced encryption technologies to safeguard personal and financial information, thereby fostering a safe gaming atmosphere.

Moreover, many online casinos are committed to responsible gaming practices, providing tools and resources for players to manage their gambling effectively. This commitment to security and player welfare ensures that players can enjoy their gaming experience without unnecessary worry.

  • Highly regulated environments for player protection
  • Encryption technologies safeguarding personal data
  • Responsible gaming resources available for support

Why choose new online casinos in Canada

Choosing new online casinos in Canada offers players an exciting and rewarding gaming experience. With a focus on innovative games, generous bonuses, and robust security measures, these platforms are tailored to meet the needs of modern players. As the online casino landscape continues to evolve, players can find a myriad of options that cater specifically to their preferences, ensuring that every gaming session is thrilling and enjoyable.

Embrace the adventure and excitement that new online casinos bring in 2026. With trustworthy platforms, fast payouts, and enticing promotions, players are invited to dive into a world of entertainment and rewards.

Getting started with Fast Withdrawal Casino UK 2026: Steps for seamless deposits and rapid



As online gaming evolves, players increasingly seek out fast withdrawal casinos to enhance their gaming experience in the UK. The year 2026 brings a fresh wave of options for players looking to enjoy fast withdrawal casino uk real money quick payouts and secure transactions. This guide highlights essential steps for getting started with fast withdrawal casinos, ensuring seamless deposits, and reaping the rewards of instant payments.

What players should compare before they deposit

When selecting a fast withdrawal casino, players must consider various factors that can impact their gaming experience significantly. The first essential aspect to evaluate is the range of payment methods available. Players should look for platforms that support multiple payment options, particularly those that enable instant withdrawals, such as cryptocurrency or e-wallets. These methods often provide quicker access to winnings compared to traditional banking methods.

Additionally, licensing and regulation should not be overlooked. Choosing a UKGC-licensed casino guarantees a level of security and trustworthiness, giving players added peace of mind. Players should also consider the game library and bonuses available, as these can enhance the overall gaming experience and provide additional opportunities for winning.

How to get started with a fast withdrawal casino

Getting started with a fast withdrawal casino involves a few straightforward steps that can smooth your gaming journey. Follow these steps to ensure a streamlined process right from the beginning.

  1. Create an Account: Visit your chosen casino’s website and complete the registration form by providing personal details.
  2. Verify Your Details: Submit required documents to confirm your identity, ensuring compliance with UK gambling regulations.
  3. Make a Deposit: Choose a preferred payment method, such as e-wallets or cryptocurrencies, to fund your account.
  4. Select Your Game: Browse through the casino’s extensive game library to find your favorite titles or explore new options.
  5. Start Playing: Enjoy your gaming experience knowing that fast withdrawals are available when you win.
  • Creating an account is quick and easy, allowing you to start playing sooner.
  • Verification enhances security and ensures fair play.
  • Using fast payment methods facilitates instant access to your funds.

Practical details for choosing the right fast withdrawal casino

When selecting a fast withdrawal casino in 2026, players should focus on several practical details that can enhance their gaming experience. Look for casinos that have established a reputation for reliable and swift payouts. For instance, casinos like LegendsPalace offer instant withdrawals via Open Banking and PayPal, ensuring players access their winnings quickly. Casinos like Hadesbet are also noteworthy, providing instant withdrawals with cryptocurrencies and quick payment options for e-wallets.

Additionally, checking each casino’s game library is crucial. A diverse selection not only keeps the gaming experience fresh but is also an indicator of the casino’s commitment to quality. Platforms with over 5,000 games, such as LuckyWave, can cater to various player preferences, from slots to table games.

  • Choose casinos with a strong track record for fast payouts.
  • Verify the range of games available to ensure variety in choice.
  • Look for trusted payment methods to enhance transaction security.

Understanding these practical details will aid players in making informed decisions, setting the stage for an enjoyable gaming experience.

Key benefits of fast withdrawal casinos

Engaging with fast withdrawal casinos offers numerous benefits that can significantly enhance the overall gaming experience. One primary advantage is the speed of transactions, allowing players to receive their winnings almost instantly. This promptness ensures that players can enjoy their earnings without unnecessary delays and return to gaming swiftly.

Another notable benefit is the range of secure payment methods that these casinos typically offer, such as e-wallets and cryptocurrencies. These options safeguard personal and financial information, providing peace of mind while transacting online. Additionally, many fast withdrawal casinos also present enticing bonuses and promotions, which can amplify a player’s budget and extend their gaming time.

  • Instant access to winnings enhances overall satisfaction.
  • Secured payment options protect personal information.
  • Attractive bonuses can lead to increased gaming funds.
  • Diverse game options cater to varied player interests.

Trust and security in fast withdrawal casinos

Trust and security are paramount for players venturing into the online casino world. Fast withdrawal casinos in the UK often operate under strict regulations, such as those enforced by the UK Gambling Commission (UKGC). This regulatory body ensures that casinos adhere to specific standards for fairness and security, creating a safer environment for players.

Moreover, licensed casinos typically employ advanced encryption technologies to protect players’ data and transactions. This added layer of security is vital in today’s digital age, where cyber threats are prevalent. Players should always verify a casino’s licensing information and look for online reviews to gauge the experiences of other users regarding trustworthiness and payout reliability.

Why choose a fast withdrawal casino in 2026

Choosing a fast withdrawal casino in 2026 opens up opportunities for a more rewarding gaming experience. With quick transactions, a broad selection of games, and secure payment options, players can focus on what truly matters—enjoying their gameplay. Fast withdrawal casinos are also likely to offer competitive bonuses, further enhancing the chances of winning.

Ultimately, selecting a reputable fast withdrawal casino can lead to a more enjoyable and hassle-free gaming experience. With the right platform, players can indulge in their favorite games while ensuring their winnings are accessible, allowing for both excitement and satisfaction.

Online Pokies NZ features you can’t miss: a deep dive into the best games



As the world of online gaming continues to evolve, online pokies in New Zealand have garnered significant attention from players seeking thrilling, real money slot experiences. With a fantastic array of games, bonuses, and secure payment methods, the landscape for online casino real money nz is vibrant and inviting. In this article, we’ll explore essential features of online pokies, including exciting game options, attractive promotions, and strategies for optimal gameplay.

How beginners can approach Online Pokies NZ

Diving into the world of online pokies can be exhilarating yet overwhelming for newcomers. Understanding the basics is crucial for maximizing the gaming experience. Players should familiarize themselves with the types of games available, the best online casinos to play at, and the various bonuses that enhance gameplay. New Zealand casinos are known for their welcoming atmosphere, making it easier for beginners to start their gaming journey.

Moreover, understanding important aspects such as return-to-player (RTP) rates and payout mechanisms can significantly impact a player’s enjoyment and success. Take the time to explore and learn the nuances of different pokies to better inform your choices.

How to get started with Online Pokies

Beginning your adventure with online pokies is a straightforward process. Follow these simple steps to ensure a smooth start:

  1. Create an Account: Visit your chosen New Zealand casino and fill out the registration form.
  2. Verify Your Details: Confirm your identity by providing the necessary documents as per the casino’s requirements.
  3. Make a Deposit: Select your preferred payment method and deposit funds into your account. Minimum deposits in leading casinos often start at NZ$30.
  4. Select Your Game: Browse the various pokies offerings, focusing on themes and features that appeal to you.
  5. Start Playing: Spin those reels and enjoy the excitement of online pokies!
  • Quick account setup ensures you can start quickly.
  • Verification boosts your account’s security.
  • Flexible payment methods enhance convenience.

Practical details for enjoying Online Pokies in New Zealand

In addition to understanding the basics, effectively engaging with online pokies requires attention to specific details that enhance the overall experience. One of the highlights of playing at New Zealand’s top online casinos, such as Neospin Casino, is the lucrative welcome bonus. Players can take advantage of bonuses up to NZ$11,000 plus 300 free spins spread across the first four deposits. This generous offer allows players to explore the games with added excitement and minimal risk.

Furthermore, players should be aware of the secure payment options available, including credit cards, MiFinity, and various cryptocurrencies. These payment methods ensure fast payouts and high levels of security, allowing players to focus on enjoyment without worrying about their funds.

  • Welcome bonus of 300% up to NZ$11,000.
  • Availability of free spins to extend gameplay.
  • Fast and secure payment methods for peace of mind.

Taking these aspects into account will ensure you make the most of your online pokies experience. The right approach not only enhances your enjoyment but also increases your chances of success.

Key benefits of playing Online Pokies

Choosing to play online pokies comes with a plethora of benefits tailored to enhance player enjoyment and engagement. Here are some key advantages that make online pokies a popular choice among New Zealand players:

  • Diverse Game Selection: A vast array of themes and gameplay mechanics caters to all preferences.
  • High RTP Slots: Many online pokies offer generous return-to-player rates, increasing the potential for winnings.
  • Attractive Bonuses: Welcome bonuses and promotions significantly boost your bankroll.
  • Accessibility: Play from the comfort of your home or on-the-go with mobile-friendly platforms.

These benefits not only enhance the overall gaming experience but also provide players with opportunities for increased enjoyment and potential winnings.

Trust and security in Online Casinos

When it comes to online gaming, ensuring trust and security is paramount. Reputable online casinos in New Zealand are licensed and regulated, providing a safe environment for players. Look for casinos that use advanced encryption technologies to safeguard personal and financial information. These security measures are essential in building trust between players and the casino operators.

Additionally, it’s wise to choose casinos with positive player reviews and transparent terms. This attention to detail helps ensure that you are engaging with a trustworthy platform, ultimately enhancing your gaming experience.

Why choose Online Pokies NZ?

Online Pokies NZ stands out as an excellent choice for players seeking thrilling and rewarding experiences. With a user-friendly approach, attractive bonuses, and a commitment to security, this platform allows players of all levels to feel included and valued. Whether you’re a seasoned player or a total newcomer, the opportunities for enjoyment are endless.

As you explore the exciting world of online pokies, remember to take advantage of the bonuses and promotions available. By making informed choices and engaging with reputable casinos, you’re setting yourself up for a fun and potentially lucrative experience. Happy spinning!

Claim exclusive promotions at Best Online Pokies Australia: your ticket to big wins



Online casinos have revolutionized the gambling landscape, providing players with the thrill of spinning the reels from the comfort of their homes. With the growth of online pokies in Australia, players are now presented with an array of opportunities that include real money options, fantastic welcome bonuses, and an impressive selection of games. As we explore how these elements enhance the gaming experience, it’s essential to consider how secure banking options and fast withdrawals contribute to a seamless journey for every player, especially when they choose to play at a trusted casino online that prioritizes user satisfaction and security.

How bonuses, games, and payouts shape the experience

In the competitive world of online casinos, bonuses, game selection, and payout structures play a crucial role in attracting and retaining players. Welcome bonuses are often the first incentive players come across, and in 2026, many platforms offer generous deals, such as up to 7500 AUD plus 250 free spins. This not only boosts a player’s initial bankroll but also provides ample opportunities to explore various games without risking too much of their own money.

The vast selection of over 8000 slots available in the Australian online pokies market ensures that players are never short of options. From classic fruit machines to modern video slots with immersive themes and engaging features, there truly is something for everyone. The payout rates also matter, as higher return-to-player (RTP) percentages can significantly impact a player’s long-term success. Understanding these aspects is vital for anyone looking to enhance their gaming experience.

How to start playing online pokies

Getting started with online pokies is straightforward, and following these steps can ensure a smooth transition into the exciting world of online gambling:

  1. Create an Account: Sign up on your chosen online casino by providing the required details and verifying your identity.
  2. Verify Your Details: This step often includes submitting identification documents to ensure compliance with regulations.
  3. Make a Deposit: Choose a secure payment method, such as Visa, Mastercard, or even crypto, with a minimum deposit starting at just 10 AUD.
  4. Select Your Game: Browse the extensive library of slots and pick a game that catches your interest.
  5. Start Playing: Enjoy your selection, and remember to take advantage of any available bonuses to maximize your play.
  • Enjoy a diverse range of games right away.
  • Take advantage of the welcome bonus to extend your playtime.
  • Quick and easy account setup means you can get to the fun faster.

Practical details for online casinos in Australia

Online casinos in Australia cater to a wide range of players by providing real money pokies that feature secure banking options and fast withdrawal processes. Understanding how the payment systems work can enhance your experience significantly. Many platforms today support various payment methods, including traditional methods like Visa and Mastercard, as well as modern options like crypto and PayID, ensuring players can choose what suits them best. Fast withdrawals are also a standout feature, allowing players to access their winnings quickly—often within hours.

  • Over 8000 slots give players innumerable choices for entertainment.
  • Real money play enhances the thrill and potential for winnings.
  • Various banking options ensure secure transactions and convenient deposits.

The combination of an extensive game library and efficient banking options creates a seamless and enjoyable gaming experience, making online pokies an attractive choice for players in 2026.

Key benefits of playing online pokies

Playing online pokies in Australia offers several key advantages that enhance the overall gaming experience. Firstly, the availability of substantial welcome bonuses serves as a great incentive for new players to join and explore what the casino has to offer. These bonuses can significantly increase the amount of money players can wager, giving them more chances to hit winning combinations.

  • High payout rates lead to better winning potential.
  • Access to hundreds of exciting themes and innovative features keeps gameplay fresh.
  • Convenient banking methods cater to various player preferences.
  • Flexible deposit options, with minimum deposits as low as 10 AUD, make it accessible for all budgets.

These benefits, coupled with the excitement of playing for real money, make online pokies a compelling choice for players looking to maximize their entertainment value.

Trust and security in online gaming

As online gaming continues to grow in popularity, trust and security are paramount for players. Reputable online casinos operate under strict regulations and hold licenses that ensure fair gaming practices. This means players can engage with peace of mind, knowing that their personal information and funds are protected. Additionally, secure banking methods add another layer of safety, allowing transactions to be completed without risk.

Moreover, cryptocurrencies are becoming increasingly popular among players for their anonymity and security benefits, making deposits and withdrawals even more secure. As players engage with online pokies, understanding the trustworthiness of the platform they choose is essential for a positive gambling experience.

Why choose online pokies in Australia?

With the unique blend of extensive game choices, generous bonuses, and secure banking options, online pokies in Australia present a compelling case for entertainment seekers in 2026. Players can enjoy an immersive gaming experience filled with excitement and potential rewards, all from the comfort of their own homes. The vibrant online gaming community ensures that there is always something new to explore, making it a fantastic avenue for both new and veteran players alike.

In conclusion, the world of online pokies offers numerous advantages for Australian players. Whether you’re attracted by the enticing welcome bonuses, the thrill of spinning the reels with real money, or the safety provided by trusted platforms, there’s no better time to start your gaming journey.

Fast and secure banking methods at Best Online Casino Australia: your guide to easy



When it comes to enjoying the best online casino experience in Australia, understanding banking methods is crucial. This guide will help you navigate the various fast and secure banking options available at leading online casinos, ensuring your gaming experience is smooth and enjoyable. For those looking to have fun with online pokies real money , knowing what to expect can enhance your gaming sessions and provide peace of mind.

What new users should expect from Best Online Casino Australia

New players stepping into the world of online casinos in Australia can expect a thrilling gaming journey filled with countless real money pokies and enticing promotions. With a variety of games at their fingertips and robust welcome bonuses, players can dive into exciting gameplay almost immediately. The best online casinos are designed to offer easy navigation, comprehensive customer support, and a secure environment, making it easy for users to focus on what matters most: playing and winning!

Additionally, new users will find a plethora of banking methods tailored to Australian players, including traditional options like credit cards and modern solutions like cryptocurrency. This versatility ensures that players can choose the payment method that best suits their needs, whether they prioritize speed, security, or convenience.

How to get started with online casinos

Getting started with an online casino is a straightforward process. By following these simple steps, you can quickly set up your account and begin your gaming adventure.

  1. Create an Account: Sign up by providing your email and creating a password.
  2. Verify Your Details: Follow the verification process to ensure your identity.
  3. Make a Deposit: Choose your preferred banking method, such as Visa, Mastercard, or crypto, and deposit at least 10 AUD.
  4. Select Your Game: Explore the vast selection of real money pokies available.
  5. Start Playing: Place your bets and enjoy the gaming experience!
  • Instant access to games.
  • Secure account setup process.
  • Variety of banking options for deposits.

Practical details for online casino banking

When it comes to banking methods at online casinos in Australia, understanding the available options is essential. Most reputable casinos offer several popular payment methods, each with its own advantages. Credit and debit cards, such as Visa and Mastercard, are widely accepted and provide instant deposits. However, for those seeking faster withdrawals, options like PayID or cryptocurrencies can significantly enhance your experience, allowing for withdrawals in as little as 0-24 hours.

Additionally, platforms that accept Neosurf enable players to deposit funds anonymously, adding a layer of privacy. It’s important to note that the minimum deposit amount is typically set at a very accessible 10 AUD, making it easy for anyone to get started. As you choose your preferred method, consider factors such as transaction speed, security, and any associated fees, ensuring you select the option that aligns best with your gaming habits.

  • Instant deposits with Visa or Mastercard.
  • Fast withdrawals using cryptocurrency or PayID.
  • Anonymous payments through Neosurf.

By familiarizing yourself with these banking details, you will be better equipped to make informed choices that enhance your overall gaming experience.

Key benefits of online casino banking methods

Understanding the benefits of various banking methods can significantly impact your online casino experience. Each method offers unique advantages that cater to different player preferences and needs. For instance, traditional banking methods provide familiarity and widespread acceptance, while newer options like cryptocurrencies offer unique features appealing to tech-savvy players.

  • Enhanced security with encryption protocols.
  • Real-time transaction processing for instant play.
  • Diverse options to suit different player preferences.
  • Flexible withdrawal speeds to meet player needs.

Choosing the right banking option will not only enhance your gaming experience but also provide you with a sense of security and satisfaction as you play.

Trust and security at online casinos

Trust and security are paramount when selecting an online casino. Players should ensure that the platform they choose is licensed and regulated, with appropriate measures in place to safeguard personal and financial information. Many reputable online casinos are licensed under authorities such as Curaçao or Anjouan, which are known for maintaining high standards in the gaming industry.

Moreover, implementing SSL encryption technology protects user data from unauthorized access. Additionally, a Random Number Generator (RNG) ensures fair gameplay, providing assurance that the outcomes of games are unbiased and random. Always check for these security features before depositing money to ensure a safe gaming environment.

  • SSL encryption for data protection.
  • Licensed by reputable authorities.
  • Regular audits to ensure fair play.

Why choose the best online casino for Australian players

Choosing the right online casino is a crucial step towards an enjoyable gaming experience. The best online casinos offer a well-rounded package, including extensive game selections, generous welcome bonuses, and robust customer support. With bonuses reaching up to $7,500 plus 100 free spins, players have ample opportunities to maximize their winnings and enhance their gameplay.

Additionally, focusing on fast and secure banking methods enables players to deposit and withdraw effortlessly, ensuring that nothing stands in the way of enjoying their favorite games. Opting for a casino that prioritizes user experience, security, and diverse payment options will not only enhance your gaming journey but also help you feel confident as you immerse yourself in the excitement of online gaming.